import lodash from 'lodash'
import eu from './enum.js'
import {parseInt} from 'lodash/string.js'
import pd, {split} from './pandas.js'
import Util from './util.js'
import {unref} from 'vue'

let app = window.Application

function openOfficeFileFromSystemDemo(param) {
    let jsonObj = (typeof (param) == 'string' ? JSON.parse(param) : param)
    alert('从业务系统传过来的参数为：' + JSON.stringify(jsonObj))
    return {wps加载项项返回: jsonObj.filepath + ', 这个地址给的不正确'}
}

export const goto = (sheet_name: string, row: number, col = 1) => {
    const sheet = get_sheet_by_name(sheet_name) || app.ActiveSheet
    app.Goto(sheet.Cells.Item(row, col), true)
}

function InvokeFromSystemDemo(param) {
    let jsonObj = (typeof (param) == 'string' ? JSON.parse(param) : param)
    let handleInfo = jsonObj.Index
    switch (handleInfo) {
        case 'getDocumentName': {
            let docName = ''
            if (Application.ActiveWorkbook) {
                docName = Application.ActiveWorkbook.Name
            }

            return {当前打开的文件名为: docName}
        }

        case 'newDocument': {
            let newDocName = ''
            let doc = Application.Workbooks.Add()
            newDocName = doc.Name

            return {操作结果: '新建文档成功，文档名为：' + newDocName}
        }

        case 'OpenFile': {
            let filePath = jsonObj.filepath
            // @ts-ignore
            Application.Workbooks.OpenFromUrl(filePath)
            return {操作结果: '打开文件成功'}
        }
    }

    return {其它xxx: ''}
}

function ColumnChar2Num(y) {
    /**
     * 将Excel中以字母表示的列名转换为数字，因为程序中只能识别以数字排序的列，如A表示第1列
     * @type {string}
     */
    y = y.trim()
    y = y.toUpperCase()
    if (y.length === 1) {
        y = y.charCodeAt() - 64 // 'A'的编码是65，'A'列即第1列，excel中从(1,1)开始，没有(0,0)单元格。
    } else if (y.length === 2) {
        let _ = y[0]
        _ = (_.charCodeAt() - 64) * 26
        y = y[1].charCodeAt() - 64 + _
    } else {
        alert('列索引超范围，目前只支持A-ZZ之间的列索引，出错列索引为：' + y)
    }
    return y
}

export function Num2ColumnChar(y) {
    /**
     * 将数字索引转换为Excel的列明，如第1列返回'A'
     */

    if (typeof y === 'string') {
        return y
    }
    let res = ''
    let x = Math.floor(y / 26)
    if (x > 0) {
        res = String.fromCharCode(x + 64)
    }
    x = y % 26
    res = res + String.fromCharCode(x + 64)
    return res
}

export function get_cell_value_by_pos(pos) {
    /**
     * 根据位置字符串表示的单元的值，如pos="原始数据!E20", pos="E20"等
     * @private
     */
    let _ = pos.split('!')
    let sheet_name = null
    if (lodash.isArray(_) && _.length === 1) {
        sheet_name = null
    } else {
        sheet_name = _[0]
    }
    let row = get_row_from_pos_string(pos)
    let col = get_col_from_pos_string(pos)
    let res = get_cell_value(row, col, sheet_name)
    return res
}

export function get_cell_value(row, col, sheet_name = undefined, return_cell = false): any {
    /**
     * 获取ET表格中（x,y）单元格的值，excel中的单元格下表从（1,1）开始，即A列的列编号为1，不是0
     * 返回值是字符串，如果为null，则转换为空字符串""返回，示例：
     * get_cell_value(2, 'B')  // 获得当前活动数据表第B2单元格的内容
     */
    let cells
    if (sheet_name) {
        // 如果指定了sheet名，则获取指定sheet上的数据
        try {
            cells = app.Worksheets.Item(sheet_name).Cells
        } catch (TypeError) {
            console.log('获取单元格(' + row + ', ' + col + ')内容失败，sheet_name = ' + sheet_name)
            return ''
        }
    } else {
        // 否则使用当前激活sheet上的数据
        cells = app.Cells
    }
    if (typeof col === 'string') {
        col = ColumnChar2Num(col)
    }
    let res = cells.Item(row, col)
    if (!return_cell) { // 如果需要的是值，则进一步取值
        res = res.Value2
    }
    if (res === null) {
        res = ''
    }
    return res
}

/**
 * 设置单元格格式
 * @param format: 格式，如"0.00"
 */
export function set_cell_format(row, col, format, sheet_name = null) {
    /**
     * 设置单元格格式
     * @param format: 格式，如"0.00"
     */
    let cell = get_cell_value(row, col, sheet_name, true)
    cell.NumberFormatLocal = format
}

function set_cell_value(x, y, value, sheet_name = null) {
    /**
     * 设置ET表格中（x,y）单元格的值
     * 示例：
     * set_cell_value(2, 'B', 12)  // 将当前数据表中B2单元格的内容设置为12
     */
    let cells
    if (sheet_name) {
        // 如果指定了sheet名，则获取指定sheet上的数据
        cells = app.Worksheets.Item(sheet_name).Cells
    } else {
        cells = app.Cells
    }

    if (typeof y === 'string') {
        y = ColumnChar2Num(y)
    }
    cells.Item(x, y).Value2 = value
}

function set_cell_formula(x, y, formula, sheet_name = null) {
    /**
     * 设置ET表格中（x,y）单元格的公式，示例：
     * set_cell_formula(2, "B", "=B1*10") // 将当前数据表中B2单元格的公式设置为"=B1*10")
     */
    let cells
    if (sheet_name) {
        // 如果指定了sheet名，则获取指定sheet上的数据
        cells = get_sheet_by_name(sheet_name).Cells
    } else {
        cells = app.Cells
    }

    if (typeof y === 'string') {
        y = ColumnChar2Num(y)
    }
    cells.Item(x, y).Formula = formula
}


export function RGB(r, g, b) {
    this.r = r
    this.g = g
    this.b = b
}

export function set_cell_color(x, y, bg_color, fg_color = undefined) {
    /**
     * 设置单元格前景色和背景色.
     * ColorIndex = 3  是红色，4是绿色，5是蓝色，6是黄色，颜色可用WPS中的JS调试器测试
     * ColorIndex = (255,0,0)是红色
     * @param bg_color: 背景颜色，可取值：3,4,5,6或(255,0,0)等
     * @param fg_color: 前景色，可取值：3,4,5,6或(255,0,0)等
     */
    if (bg_color) {
        if (typeof (bg_color) === 'object') { // 如果bg_color是RGB类型
            get_cell_object(x, y).Interior.ColorIndex = (bg_color.r, bg_color.g, bg_color.b)
        } else {
            get_cell_object(x, y).Interior.ColorIndex = bg_color
        }
    }
    if (fg_color) {
        if (typeof (bg_color) === 'object') {
            get_cell_object(x, y).Font.ColorIndex = (bg_color.r, bg_color.g, bg_color.b)
        } else {
            get_cell_object(x, y).Font.ColorIndex = bg_color
        }
    }
}

export function set_cells(x, y, data, sheet_name?: string) {
    /**
     * 将 二维列表data 写入（x,y）单元格开始的区域
     */
    for (let i = 0; i < data.length; i++) {
        for (let j = 0; j < data[i].length; j++) {
            set_cell_value(x + i, y + j, data[i][j], sheet_name)
        }
    }
}

export function delete_row(row_index, sheet_name) {
    /**
     * 删除工作表中的行
     */
    if (!sheet_name) {
        sheet_name = app.ActiveSheet.Name
    }
    let sheet = get_sheet_by_name(sheet_name)
    let row = sheet.Rows.Item(row_index)
    row.Delete()
}

export function delete_column(col_index, sheet_name) {
    /**
     * 删除工作表中的列
     */
    if (!sheet_name) {
        sheet_name = app.ActiveSheet.Name
    }
    let sheet = get_sheet_by_name(sheet_name)
    let column = sheet.Columns.Item(col_index)
    column.Delete()
}

export function clear_sheet(sheet_name: string) {
    let sheet = get_sheet_by_name(sheet_name)
    if (sheet) {
        sheet.Delete()
        create_new_sheet(sheet_name)
    }
}

export function get_active_sheet_name() {
    /**
     * 获取当前激活的工作表的名称
     */
    return app.ActiveSheet.Name.toString()
}

export function get_sheet_by_name(name) {
    /**
     * 获取给定名称的工作表对象，返回的是Sheet对象，如果name不存在，则返回空
     */
    let res = Object(app.Worksheets.Item(name))
    if (res.Cells !== undefined) {
        // 说明是一个object，即获得了对应的工作表
        return Object(app.Worksheets.Item(name))
    } else {
        // 此时，res={}，其具有长度属性，将其置空，表示没有获取到对象
        return null
    }
}

function get_cell_object(x, y) {
    return Object(app.Cells.Item(x, y))
}

export function exist_sheet(sheet_name: string) {
    return get_sheet_by_name(sheet_name) !== null
}

export function hide_sheet(sheet_name) {
    let sheet = get_sheet_by_name(sheet_name)
    if (sheet) {
        sheet.Visible = false
    }
}

export function show_sheet(sheet_name) {
    let sheet = get_sheet_by_name(sheet_name)
    if (sheet) {
        sheet.Visible = true
    }
}

function create_new_sheet(sheet_name, activate = true) {
    /**
     * 创建新表，默认在最后一个位置创建工作表
     * @type {Et.EtSheets}
     */
    let sheets = app.Worksheets
    let sheet = get_sheet_by_name(sheet_name)
    if (!sheet) {
        let sheet = app.Worksheets.Add(sheets.Item(sheets.Count))
        sheet.Name = sheet_name
    } else {
    }

    if (activate) {
        activate_sheet(sheet_name)
    }
    return sheet
}

export function add_title_region(sheet_name) {
    /**
     * 添加数据计算表的表头，表头包括"项目", "开始日期", "开始时间", "结束时间"
     * @type {any}
     */
    let cells = get_sheet_by_name(sheet_name).Cells

    let row = 1
    activate_sheet(sheet_name)
    let cols = getLastColumnIndexOfSheet('数据整理')
    // console.log(cols)
    for (let i = 2; i <= cols; i++) {
        let y_char = Num2ColumnChar(i)
        cells.Item(row, i).Formula = form_formula2('数据整理!{row}', y_char, row)
        cells.Item(row + 1, i).Formula = form_formula2('数据整理!{row+1}', y_char, row)
        cells.Item(row + 2, i).Formula = form_formula2('数据整理!{row+2}', y_char, row)
        cells.Item(row + 3, i).Formula = form_formula2('数据整理!{row+3}', y_char, row)
        if (i > 3) {
            cells.Item(row + 1, i).NumberFormat = 'yyyy-m-d'
            cells.Item(row + 2, i).NumberFormat = 'hh:mm'
            cells.Item(row + 3, i).NumberFormat = 'hh:mm'
        }
    }
    freezePanes(sheet_name, 5, 'D') // 冻结D5单元格
}

function createTurbineSheet(sheet_name) {
    let sheet
    if (sheet_name === undefined) {
        sheet_name = '汽机'
    }
    let sheets = app.Worksheets
    sheet = get_sheet_by_name(sheet_name)

    if (get_sheet_by_name('数据整理')) {
        // 如果当前工作簿中存在“数据整理”工作表
        if (!sheet) {
            // 如果不存在sheet_name代表的工作表，则创建
            let newSheet = app.Worksheets.Add(undefined, sheets.Item('数据整理')) // 在数据整理之后添加新sheet
            newSheet.Name = sheet_name
            // 新创建的"汽机"工作表需要添加表头
            add_title_region('汽机')
        } else {
            console.log('已经存在名为' + sheet_name + '的工作表！')
        }
    } else {
        alert('未找到“数据整理”工作表，因此无法获得计算所需的数据源，无法计算汽轮机性能，请检查！')
        return false
    }
    return true
}

export function self_debug(说明, obj) {
    console.log(说明)
    console.log(obj)
}

export function get_row_from_pos_string(pos_string) {
    /**
     * 从形如"汽机!D23"的字符串中获取行号，即23，如果pos_string是数据，则只读取数组中的第一个单元
     */
    if (!pos_string) {
        return null
    }
    if (pd.type(pos_string) === 'Array') {
        pos_string = pos_string[0]
    }
    if (pd.type(pos_string) !== 'String') {
        pos_string = pos_string.toString()
    }
    pos_string = pos_string.trim()
    if (!pos_string) {
        return null
    }
    let _sb_ = pos_string.split('!')
    let sub_pos = _sb_[_sb_.length - 1] // 取_sb_列表的最后一项
    _sb_ = sub_pos.match(/\d+/g)[0] // 取匹配到的第一个数字字符串
    if (_sb_) {
        return parseInt(_sb_)
    } else {
        return null
    }
}

export function get_col_from_pos_string(pos_string) {
    if (!pos_string) {
        return null
    }
    if (pd.type(pos_string) === 'Array') {
        pos_string = pos_string[0]
    }
    if (pd.type(pos_string) !== 'String') {
        pos_string = pos_string.toString()
    }
    pos_string = pos_string.trim()
    if (!pos_string) {
        return null
    }
    let _sb_ = pos_string.split('!')
    let sub_pos = _sb_[_sb_.length - 1] // 取_sb_列表的最后一项
    _sb_ = sub_pos.match(/[A-Za-z]+/g)[0] // 取匹配到的第一个数字字符串
    if (_sb_) {
        return _sb_.trim()
    } else {
        return null
    }
}

export function get_sheet_name_from_pos_string(pos_string, default_name: string = null) {
    /**
     * 获取pos_string字符串对应的工作表名称，如果其中没有工作表名或其他错误情况，返货默认的default_name
     */
    if (!pos_string) {
        return default_name
    }
    if (pd.type(pos_string) === 'Array') {
        pos_string = pos_string[0]
    }
    if (pd.type(pos_string) !== 'String') {
        pos_string = pos_string.toString()
    }
    pos_string = pos_string.trim()
    if (!pos_string) {
        return default_name
    }
    if (lodash.includes(pos_string, '!')) {
        let _sb_ = pos_string.split('!')
        // 取_sb_列表的最后一项
        return _sb_[0]
    } else {
        return default_name
    }

}

export function get_test_point_name(pos_string) {
    /**
     * 获取pos_string字符串对应的测点的名称
     */
    let row = get_row_from_pos_string(pos_string)
    if (pos_string.startsWith('原始数据')) {
        return get_cell_value(row, 1, '原始数据')
    } else if (pos_string.startsWith('数据整理')) {
        return get_cell_value(row, 2, '数据整理')
    }
}

export function update_project(item_k) {
    /**
     * 更新项目信息，保存
     */
    create_new_sheet('pro_dat') // 确保项目信息表存在
    let dat_str = get_cell_value(1, 1, 'pro_dat')
    let data
    let name = item_k['name']
    if (dat_str) {
        // 存在项目信息字符串
        data = JSON.parse(dat_str)
        if (data[name]) {
            // 如果已经存在该测点，则更新该测点的io属性
            data[name]['io'] = item_k['io']
        } else {
            // 如果不存在该测点，则创建该测点并设置其io属性
            data[name] = {}
            data[name]['io'] = item_k['io']
        }
    } else {
        // 如果不存在项目信息
        data = {}
        data[name] = {}
        data[name]['io'] = item_k['io']
    }
    let proj_str = JSON.stringify(data)
    set_cell_value(1, 1, proj_str, 'pro_dat')
}

function activate_sheet(sheet_name) {
    /**
     * 激活当前工作簿中的某个单元格
     * @type {string}
     */
    let sheet = get_sheet_by_name(sheet_name)
    sheet.Activate()
}

export function getLastRowIndexOfSheet(sheet_name = undefined) {
    /**
     * 获取指定工作表中数据区域的最后一行的行号。如果不传入sheet_name则默认取当前活动的sheet。该方法不会导致活动sheet改变，即可以取未激活的
     * sheet的数据区域的行号。
     */
    let sheet
    if (sheet_name === undefined) {
        sheet = app.ActiveSheet
    } else {
        let sheets = app.Worksheets
        sheet = sheets.Item(sheet_name)
    }
    if (sheet) {
        let usedRange = sheet.UsedRange
        // Application.Range("B4", sheet.Range("B4").End(wps.Enum.xlDown)).Select();
        let firstRow = usedRange.Row
        let rowCount = usedRange.Rows.Count
        return firstRow + rowCount - 1
    } else {
        console.log('不存在名为' + sheet_name + '的sheet，请检查！')
        return 0
    }
}

/**
 * 获取指定工作表中已拥有数据的最后一列的列号
 *
 * 此函数通过检查工作表的UsedRange属性来确定包含数据的最后一列。
 * 如果提供了sheet_name参数，则获取指定工作表的数据范围；否则获取当前活动工作表的数据范围。
 *
 * @param sheet_name - 工作表名称，如果不指定则使用当前活动工作表
 * @returns 最后一列的列号（基于1的索引，例如A列返回1，B列返回2，以此类推）
 *
 * @example
 * // 获取当前活动工作表的最后一列
 * const lastCol = getLastColumnIndexOfSheet();
 *
 * // 获取指定工作表的最后一列
 * const lastCol = getLastColumnIndexOfSheet("Sheet1");
 */
export function getLastColumnIndexOfSheet(sheet_name = undefined) {
    let active_sheet = app.ActiveSheet
    let sheet: any
    if (!sheet_name) {
        sheet = app.ActiveSheet
    } else {
        let sheets = app.Worksheets
        sheet = sheets.Item(sheet_name)
    }
    if (sheet) {
        let usedRange = sheet.UsedRange
        // console.log(usedRange.Address())
        // 获取使用区域的第一列和列数，计算出最后一列的索引
        let firstColumn = usedRange.Column
        let columnCount = usedRange.Columns.Count
        return firstColumn + columnCount - 1
    } else {
        console.log('不存在名为' + sheet_name + '的sheet，请检查！')
        active_sheet.Activate()
        return 0
    }
}

export function getUsedRangeOfSheet(sheet_name = undefined): {
    startRow: number,
    startCol: number,
    endRow: number,
    endCol: number
} | null {
    let active_sheet = app.ActiveSheet
    let sheet: any
    if (!sheet_name) {
        sheet = app.ActiveSheet
    } else {
        let sheets = app.Worksheets
        sheet = sheets.Item(sheet_name)
    }
    if (sheet) {
        let usedRange = sheet.UsedRange
        // console.log(usedRange.Address())
        // 获取使用区域的第一列和列数，计算出最后一列的索引
        let startCol = usedRange.Column
        let columnCount = usedRange.Columns.Count
        const endCol = startCol + columnCount - 1
        let startRow = usedRange.Row
        let rowCount = usedRange.Rows.Count
        const endRow = startRow + rowCount - 1
        return {startRow, startCol, endRow, endCol}
    } else {
        console.log('不存在名为' + sheet_name + '的sheet，请检查！')
        active_sheet.Activate()
        return null
    }
}

function extend_list_by_alternative_symbols(origin_list, alter_symbol = undefined) {
    /**
     * 将列表中的可替换字符串更换形式，从而形成更全面的字符串
     *
     *     例如：
     *     origin=["1号高加进汽压力"]
     *     origin=extend_list_by_alternative_symbols(origin, {"1": "一", "汽": "气"})
     *     则结果为：
     *     origin=['1号高加进汽压力', '一号高加进汽压力', '1号高加进气压力', '一号高加进气压力']
     */
    if (!alter_symbol) {
        alter_symbol = eu.alternative_symbol
    }
    if (typeof origin_list === 'string') {
        origin_list = [origin_list]
    }
    for (let idx in origin_list) {
        let item = origin_list[idx]
        for (let k in alter_symbol) {
            let v = alter_symbol[k]
            let new_item = item.replace(k, v)
            if (!lodash.includes(origin_list, new_item)) {
                origin_list.push(new_item)
            }
            new_item = item.replace(v, k)
            if (!lodash.includes(origin_list, new_item)) {
                origin_list.push(new_item)
            }
        }
    }
    return origin_list
}

function get_standard_point(name_list, sp) {
    /**
     *  获取给定的name_list属于哪个测点，该测点只指定了位置，不指定具体的参数，如高压主蒸汽，但是压力还是温度不确定
     *  例如:
     *  get_standard_point(["高压主汽压力", "高压主气压力"])， 返回["main_steam", "P"]
     */

        // --------------------------- 先判断name_list属于StandardPoint中哪一个测点下 --------------------------------
    let matched_pos = null
    for (let k in sp) {
        let v = sp[k]
        let include = v['include']
        let exclude = v['exclude']
        if (!include) { // 如果include为空，则进行下一次循环
            continue
        }

        for (let cur_name of name_list) { // 遍历用户传入的测点名
            // 检查是否满足任意一个包含规则
            let includeMatched = false
            for (const rule of include) {
                if (typeof rule === 'string') {
                    // 简单包含规则：如果包含该字符串则满足条件
                    if (cur_name.includes(rule)) {
                        includeMatched = true
                        break
                    }
                } else if (Array.isArray(rule)) {
                    // 复合包含规则：数组中所有条件都必须满足
                    let allMatched = true
                    for (const subRule of rule) {
                        if (subRule.startsWith('!')) {
                            // 以!开头，表示不能包含该字符串（去掉!后）
                            const keyword = subRule.slice(1)
                            if (cur_name.includes(keyword)) {
                                // 如果包含该关键词，则不满足这个子规则
                                allMatched = false
                                break
                            }
                        } else {
                            // 不以!开头，表示必须包含该字符串
                            if (!cur_name.includes(subRule)) {
                                // 如果不包含该关键词，则不满足这个子规则
                                allMatched = false
                                break
                            }
                        }
                    }

                    // 如果数组中所有子规则都满足，则满足这个包含规则
                    if (allMatched) {
                        includeMatched = true
                        break
                    }
                }
            }

            // 如果不满足任何包含规则，判断下一个名称
            if (!includeMatched) {
                continue
            }

            let go_next = false
            // 检查排除规则
            for (const rule of exclude) {
                if (typeof rule === 'string') {
                    // 简单排除规则：如果包含该字符串则排除
                    if (cur_name.includes(rule)) {
                        go_next = true // 满足了排除规则
                        break
                    }
                } else if (Array.isArray(rule)) {
                    // 复合排除规则：数组中所有条件都必须满足才排除
                    let allMatched = true
                    for (const subRule of rule) { // ['燃料', ['FGH', '!含FGH流量']] cur_name = '中压给水流量（含FGH流量）'
                        if (subRule.startsWith('!')) { //
                            // 以!开头，表示不能包含该字符串（去掉!后）
                            const keyword = subRule.slice(1)
                            if (cur_name.includes(keyword)) {
                                // 如果包含该关键词，则不满足这个子规则
                                allMatched = false // 中压给水流量（含FGH流量）包含了 含FGH流量，这里直接break
                                break
                            }
                        } else {
                            // 不以!开头，表示必须包含该字符串
                            if (!cur_name.includes(subRule)) {
                                // 如果不包含该关键词，则不满足这个子规则
                                allMatched = false
                                break
                            }
                        }
                    }
                    if (allMatched) { // 说明子元素满足，则排除掉当前元素属于该测点
                        go_next = true // 满足了排除规则
                        break
                    }
                }
            }

            if (!go_next) { // 说明当前元素属于该测点
                matched_pos = k
                break
            }
        }

        if (matched_pos !== null) {
            break
        }
    }
    // --------------------------- 先判断name_list属于StandardPoint中哪一个测点下 --------------------------------
    if (matched_pos === null) {
        return null
    } else if (matched_pos in ['regulator_station', 'fgh_gas_in', 'fgh_gas_out', 'fgh_water_in', 'fgh_water_out',
        'generator_gas_turbine', 'hrsg_flue_out', 'feed_water_mid', 'feed_water_low']) {
        wps.PluginStorage.setItem('group_type', '联合循环1')
    }
    // --------------------------- 匹配具体的值类型 --------------------------------------------
    let _name = name_list[0]
    let para
    if (lodash.includes(_name, '压力') || lodash.includes(_name, '背压')) {
        para = 'P'
    } else if (lodash.includes(_name, '温度')) {
        para = 'T'
    } else if (lodash.includes(_name, '差压') && !lodash.includes(_name, '差压计算')) { // 差压计算的一般是流量
        para = 'dp'
    } else if (lodash.includes(_name, '脉冲数')) {
        para = 'Pulse'
    } else if (lodash.includes(_name, '体积流量')) {
        if (lodash.includes(_name, '标况')) // 标况下的天然气体积流量
            para = 'Q_std'
        else
            para = 'Q'
    } else if (lodash.includes(_name, '当量流量')) { // 水位当量流量
        para = 'F_lvl'
    } else if (lodash.includes(_name, '流量')) {
        para = 'F'
    } else if (lodash.includes(_name, '焓值')) {
        para = 'H'
    } else if (lodash.includes(_name, '熵值')) {
        para = 'S'
    } else if (lodash.includes(_name, '功率因数')) {
        para = 'factor'
    } else if (lodash.includes(_name, '功率')) {
        para = 'power'
    } else if (lodash.includes(_name, '频率')) {
        para = 'freq'
    } else if (lodash.includes(_name, '电压')) {
        para = 'U'
    } else if (lodash.includes(_name, '电流')) {
        para = 'I'
    } else if (lodash.includes(_name, '面积')) {
        para = 'area'
    } else if (lodash.includes(_name, '效率')) {
        para = 'eta'
    } else if ( // 水位测点单独处理
        matched_pos === 'condenser_water_lvl' || matched_pos === 'deaerator_water_lvl') {
        if (lodash.includes(_name, '开始')) {
            para = 'start'
        } else if (lodash.includes(_name, '结束')) {
            para = 'end'
        }
    } else if (matched_pos === 'datetime') {  // 日期时间测点单独处理
        if (lodash.includes(_name, '日期')) {
            para = 'start_date'
        } else if (lodash.includes(_name, '开始')) {
            para = 'start'
        } else if (lodash.includes(_name, '结束')) {
            para = 'end'
        }
    } else if (matched_pos === 'leakage_known') {
        para = 'F'
    } else if (matched_pos === 'leakage_unknown') {  // 不明漏量测点单独处理
        if (lodash.includes(_name, '占比') || lodash.includes(_name, '百分比')) {
            if (lodash.includes(_name, '炉侧') || lodash.includes(_name, '锅炉')) {
                para = 'percentage_boiler'
            } else {
                para = 'percentage_turbine'
            }
        }
    } else if (matched_pos === 'environment') {
        if (lodash.includes(_name, '湿度')) {
            para = 'RH'
        }
    } else if (matched_pos === 'fuel_gas') {
        if (lodash.includes([
            'CH4', 'N2', 'CO2', 'C2H6', 'C3H8',
            'H2O', 'H2S', 'H2', 'CO', 'O2',
            'i-C4H10', 'n-C4H10', 'i-C5H12', 'n-C5H12', 'C6H14',
            'C7H14', 'C8H14', 'C9H14', 'C10H14', 'He',
            'Ar'
        ], _name)) {
            para = _name
        } else if (lodash.includes(_name, '热值')) {
            para = 'LHV'
        }
    } else if (matched_pos === 'generator_turbine' || matched_pos === 'generator_st' || matched_pos === 'generator') {
        if (lodash.includes(_name, '氢压')) {
            para = '氢压'
        } else if (lodash.includes(_name, '转速')) {
            para = 'rpm'
        }
    }

    // --------------------------- 匹配具体的值类型 --------------------------------------------

    return [matched_pos, para]
}

function dispatch_point_to_para(var_name, sp) {
    /**
     * 获取var_name所属的测点名，例如:
     * dispatch_point_to_para("高压主蒸汽压力")，返回["main_steam", "P"]
     */
        // 首先获得用户输入测点名的可能变化，如：1号高加=一号高加
    let name_list = extend_list_by_alternative_symbols(var_name)
    // 获取对应的标准测点名
    return get_standard_point(name_list, sp)
}

function dispatch_point<T>(var_name, i, sheet_name, sp: T): T {
    /**
     * 将用户自定义的变量参数归类到标准测点清单中，var_name为试验期间 用户输入的测点名，例如"高压主汽温度"。该方法操作后，每个找到的测点将会增加exists属性，用于
     * 记录机组中是否存在该标准测点。
     * 在测点名的A列添加"不准"，则可以忽略该行测点。
     * sp是标准测点对象，只能是eu.StandardPoint或者eu.StandardPointOrigin
     例如：
     dispatch_point("高压蒸汽压力", 20, "数据整理")，该语句会将eu.StandardPoint中main_steam.P参数的href设置为数据整理表中的第20行，以后使用main_steam.P引用时，会自动引用到指定位置的数据
     * @param var_name 测点名成
     * @param i 测点所属的行号
     * @returns {Array}
     */
    // 首先获得用户输入测点名的可能变化，如：1号高加=一号高加
    // console.log(`分配测点名：${var_name}, 工作表【${sheet_name}】第${i}行`)
    var_name = var_name.split('_')[0]  // 只有下划线前的字符串用来判断测点位置，后面的字符串是表号和单位，弃掉，否则会影响A、B侧之类测点的判断
    var_name = var_name.split('(')[0]  // 括号里的字符串为备注信息，也不参与测点信息判断
    let name_list = extend_list_by_alternative_symbols(var_name)
    // 获取对应的标准测点名
    let _sd = get_standard_point(name_list, sp)
    console.log(`【${var_name}】对应标准测点名为${_sd}`)
    if (_sd) {
        // if (sheet_name === "数据整理") {
        let bak_info = get_cell_value_by_pos(`${sheet_name}!A${i}`)
        if (bak_info === '参考' || lodash.includes(bak_info, '不准')) {
            return sp
        }
        // }
        let standard_point = _sd[0]
        let para = _sd[1]
        // console.log(sp[standard_point])
        if (!sp[standard_point]['href']) {
            // 如果当前测点没有href属性，就创建
            sp[standard_point]['href'] = {}
        }
        // 当前测点有href属性，但不存在对应的para参数时，创建对应参数数据
        let para_s = sp[standard_point]['href'][para]
        if (!para_s || para_s.length <= 0) {
            // 如果para_s为空值，或其长度不大于0，则初始化赋值
            sp[standard_point]['href'][para] = [sheet_name + '!{Y}' + i]
            sp[standard_point]['exists'] = true
            if (standard_point.search('heater') !== -1) {
                for (let i of ['1', '2', '3', 'pre_3', '4', '5', '6', '7', '8', '9']) {
                    let pos_name = ['heater_' + i + '_vapor_in', 'heater_' + i + '_vapor_out', 'heater_' + i + '_water_in', 'heater_' + i + '_water_out']
                    if (pos_name.indexOf(standard_point) !== -1) { // 如果发现任一个加热器的测点，则加热器存在
                        sp['heater_' + i]['exists'] = true
                        break
                    }
                }
            }

        } else {
            //如果存在对应的参数，且当前参数与现有参数不同，则添加
            if (!lodash.includes(para_s, sheet_name + '!{Y}' + i)) sp[standard_point]['href'][para].push(sheet_name + '!{Y}' + i)
        }
    }
    return sp
}

export function scanDesignPoints(sheet_name = '数据整理', sd = undefined) {
    let condition_name = get_cell_value(1, 4, sheet_name)
    if (condition_name === '设计工况') {
        // console.log(`在工作表【${sheet_name}】中，发现设计工况，开始分析标准测点清单`)
        Object.entries(sd).forEach(([key, value]) => {
            // console.log(sd[key]['href'])
            sd[key]['href'] = sd[key]['href'] || {}
            // 遍历sd[key]['href']对象
            Object.entries(sd[key]['href']).forEach(([href_key, href_value]) => {
                if (!href_key.endsWith('_design') && href_value && (href_value as string[]).length > 0) {
                    sd[key]['href'][`${href_key}_design`] = (href_value as string[]).map(item => item.replace('{Y}', 'D'))
                }
            })
        })
    } else {
        console.log(`在工作表【${sheet_name}】中，未发现设计工况，请填充设计工况数据`)
    }
    return sd
}

function refreshTestPoints(in_deep = undefined, sheet_name = '数据整理', sd = undefined) {
    /**
     * 该方法扫描”数据整理“工作表中第2列变量名，并根据参数名将excel中的引用位置链接到enum.StandardTestPoint中，以方便后续计算时直接引用测点
     * 在后续计算时则可以使用 main_steam.P 等方式引用测点的数据所在的位置。
     * @param {boolean} 是否需要递归调用以确保初始化扫描测点
     * @type {string}
     */
        // console.log(`开始扫描${sheet_name}测点信息`)
    let rows = getLastRowIndexOfSheet(sheet_name)
    console.log(`工作表【${sheet_name}】共有 ${rows} 行数据`)
    if (!sd) {
        console.log('未传入标准测点对象')
        sd = eu.StandardPoint
    }
    for (let i = 2; i < rows + 1; i++) {
        // 遍历每一行的测点名，前四行分别是”项目“，”开始日期“，”开始时间“，”结束时间“，因此从第5行开始
        let user_defined_name = get_cell_value(i, 2, sheet_name)
        if (user_defined_name) {
            // console.log('扫描前sd = ', sd)
            sd = dispatch_point(user_defined_name, i, sheet_name, sd)
            // console.log('扫描后sd = ', sd)

            if (lodash.includes(user_defined_name, '取压方式') ||
                lodash.includes(user_defined_name, '试验时长')) { // 数据整理下方的流量原件计算区域不扫描
                break
            }
        } // 将当前测点分配到标准测点清单中
    }
    return sd
}

// function refreshOriginPoints() {
//   /**
//    * 该方法扫描”数据整理“工作表中第2列变量名，并根据参数名将excel中的引用位置链接到enum.StandardTestPoint中，以方便后续计算时直接引用测点
//    * 在后续计算时则可以使用 main_steam.P 等方式引用测点的数据所在的位置。
//    * @param {boolean} 是否需要递归调用以确保初始化扫描测点
//    * @type {string}
//    */
//     // 查找“原始数据”工作表，查询对应的参数位置
//   let rows = getLastRowIndexOfSheet('原始数据')
//   for (let i = 2; i < rows; i++) {
//     // 遍历每一行的测点名，前四行分别是”项目“，”开始日期“，”开始时间“，”结束时间“，因此从第5行开始
//     let user_defined_name = get_cell_value(i, 1, '原始数据')
//     if (user_defined_name) {
//       // console.log("user_defined_name=" + user_defined_name)
//       dispatch_point(user_defined_name, i, '原始数据', eu.StandardPointOrigin)
//     } // 将当前测点分配到标准测点清单中
//   }
//   console.log('标准测点：')
//   console.log(eu.StandardPointOrigin)
//   return eu.StandardPointOrigin
// }


export function read_sheet(sheet_name) {
    /**
     * 读入工作表，并以第index_col列为行标题，如果需要读入的是dataframe类型的数据对象，则需要传入index_col作为dataframe的行标题所在列。
     *
     * header=0,                    # 指定作为列名的行号；None 表示无列名
     * names=None,                  # 自定义列名列表
     * index_col=None,              # 指定列为行索引
     * usecols=None,                # 读取哪些列（列名、列号、可调用函数）
     * @type {any}
     */
    let sheet = get_sheet_by_name(sheet_name)
    if (!sheet) {
        return null
    }

    return sheet.UsedRange.Value2
}

export function set_position_of_para(var_name, pos_str, sp) {
    /**
     * 记录某个参数在excel表格中的位置，如果该参数的watch属性有值，则处理watch属性的引用。
     * 例如：set_position_of_para("main_steam.F", "汽机!{Y}32"}
     */
    sp = unref(sp)
    var_name = var_name.replace('{', '').replace('}', '')
    let point_name = var_name.split('.')[0]
    let para = var_name.split('.')[1]
    try {
        if (!sp[point_name]) {
            console.log(`标准测点中没有${point_name}点，请检查！`)
        }
        if (!sp[point_name]['href']) {
            sp[point_name]['href'] = {}
        }
    } catch (e) {
        console.log('出错的点名为: ' + point_name + '.' + para)
        console.log(e)
    }
    let pos = sp[point_name]['href'][para]
    if (pos && pos.length > 0) {
        // 如果需要设置的点已经存在且包含当前设置的pos_str，则不进行操作
        if (lodash.includes(pos, pos_str)) {
            //
            return true
        } else {
            sp[point_name]['href'][para].push(pos_str)
        }
    } else {
        // 否则，创建当前点的引用位置列表
        sp[point_name]['href'][para] = [pos_str]
    }

    // 处理监视当前测点更新的引用
    let watch = sp[point_name].watch
    if (!watch) {
        return true
    }
    watch = watch[para]
    if (watch && pd.type(watch) === 'Array' && watch.length > 0) {
        let link_row
        let current_pos
        for (let i = 0; i < watch.length; i++) {
            link_row = watch[i].split('{Y}')[1]
            let value = (get_cell_value(link_row, 'D'))
            if (!lodash.includes(value, 'watching')) {  // 说明已经更新过相关参数，则无须再次更新
                break
            }
            for (let j = 4; j <= getLastColumnIndexOfSheet(); j++) {
                current_pos = pos_str.replace('{Y}', Num2ColumnChar(j))
                set_cell_formula(link_row, j, '=' + current_pos)
            }
        }
    }
    return true
}

export function get_position_of_para(var_name, y = undefined, used_in = undefined, sp = undefined, fetch = 'first') {
    /**
     * 获取参数的引用位置。
     *
     * 可以通过"{point.para}.(var_name:row-i)的方式向该方法内传入参数，当point.para不存在时，调用其他方法进
     * 行point.para的获取，小括号中的参数为传递给其他方法的参数。此时，其他方法可以返回一个公式，以"formula:"开头。
     *
     * @param var_name: 变量名，程序内部使用的固定的变量名，如main_steam.P表示主蒸汽压力
     * @param y: 工况所在的列的数字索引
     * @param used_in: 该引用将应用的位置，该参数用于对参数位置字符串做进一步处理，如本表引用，则删除表名的字段，本表引用为true,否则为false
     * @param fetch: 获取第几个的位置，可取值first,last,all
     * @type {string}  返回字符串形如“数据整理!A1”，如果未查找到，返回null
     */
    try {
        // console.log(sp)
        if (y) {
            if (typeof y !== 'string') {
                y = Num2ColumnChar(y)
            }
        }
        var_name = var_name.replace('{', '').replace('}', '')
        let point_name = var_name.split('.')[0]
        let para = var_name.split('.')[1]
        if (!sp[point_name]) {
            console.log(`标准测点中没有${point_name}点，请检查！point_name = ${point_name}`)
            console.log(sp)
        }
        if (!sp[point_name]['href']) {  // 如果sp[point_name]["href"]为空，则初始化为空字典
            sp[point_name]['href'] = {}
        }
        let pos_str = sp[point_name]['href'][para]
        if (!pos_str || !String(pos_str)) { // 如果pos_str为空，则继续转换
            // pos_str可能为多种类型的空值，如空字典，undefined等
            // pos_str可能返回空列表，即[]。
            let link = sp[point_name].link // 查找当前点的替代点，如一号高加出水就是二号高加进水
            if (link && link.length > 0) {
                for (let i in link) {
                    let alter_id = link[i]
                    pos_str = sp[alter_id]['href'][para]
                    if (pos_str && pos_str.length > 0) {
                        console.log('未找到' + var_name + '对应的参数！但发现替代点' + alter_id + '.' + para)
                        sp[point_name]['href'][para] = pos_str
                        if (fetch === 'all') {
                            let res = []
                            for (let _pos of pos_str) {
                                _pos = _pos[0].replace('{Y}', y)
                                _pos = _pos.replace(used_in + '!', '') // 当前表引用当前表，不需要表名
                                res.push(_pos)
                            }
                            return res
                        } else if (fetch === 'last') {
                            pos_str = pos_str[pos_str.length - 1].replace('{Y}', y)
                            pos_str = pos_str.replace(used_in + '!', '') // 当前表引用当前表，不需要表名
                            return pos_str
                        } else {
                            pos_str = pos_str[0].replace('{Y}', y)
                            pos_str = pos_str.replace(used_in + '!', '') // 当前表引用当前表，不需要表名
                            return pos_str
                        }
                    }
                }
            }
            if (point_name === 'leakage') {
                let obj = sp.leakage.obj
                let vars = var_name.split(para)[1]
                vars = vars.substring(2, vars.length - 1)
                let _sd = vars.split(':')
                let idx_row
                if (_sd[0] === 'main_steam.F') {
                    idx_row = _sd[1].replace('{Y}', y)
                }
                let leakage = obj.get_leakage(para, idx_row)
                if (!leakage) {
                    return null
                }
                leakage = 'formula:' + leakage
                return leakage
            }
            console.log('未找到' + var_name + '对应的参数！')
            return null
        } else {
            if (pos_str.length === 1) {  // 当只有一个链接时，fetch_all为true还是false都没有意义
                fetch = 'first'
            }
            if (fetch === 'all') {
                let res = []
                for (let pos_ of pos_str) {
                    if (y) {
                        pos_ = pos_.replace('{Y}', y)
                    }
                    if (used_in) {
                        // 如果used_in不为空，则进行以下操作
                        pos_ = pos_.replace(used_in + '!', '') // 当前表引用当前表，不需要表名
                    }
                    res.push(pos_)
                }
                return res
            } else {
                if (fetch === 'last') {
                    pos_str = pos_str[pos_str.length - 1]
                } else {
                    pos_str = pos_str[0]
                }
                if (y) { // 如果传入了具体的列索引，则替换{Y}为列索引
                    pos_str = pos_str.replace('{Y}', y)
                }
                if (used_in) {
                    // 如果used_in不为空，则进行以下操作
                    pos_str = pos_str.replace(used_in + '!', '') // 当前表引用当前表，不需要表名
                }
                return pos_str
            }
        }
    } catch (e) {
        console.log('获取参数引用位置时发生错误，此时引用的点名为：' + var_name + '，当前的StandardPoint对象如下：')
        console.log(sp)
        console.log(e)
    }
}

export function get_value_of_point(para_str, y, sp) {
    /**
     * 获取点名对应的值，如main_steam.P返回主蒸汽压力的值
     * @private
     */
    let pos_str = get_position_of_para(para_str, y, '', sp)
    if (!pos_str) {
        console.log('未找到' + para_str + '，请确认！')
        return null
    }
    let _sd = pos_str.split('!')
    let sheet_name = _sd[0]
    let x = parseInt(_sd[1].match(/\d+/)[0])
    return get_cell_value(x, y, sheet_name)
}

export function form_formula(func_str, paras_list) {
    if (!func_str.startsWith('=')) {
        func_str = '=' + func_str
    }
    if (typeof paras_list === 'string') {
        return func_str + '(' + paras_list + ')'
    } else {
        let paras_str = paras_list.join(',')
        return func_str + '(' + paras_str + ')'
    }
}

export function form_formula1(func_str, y, sp) {
    /**
     * 替换{main_steam.F}之类的参数
     */
    if (!func_str.startsWith('=')) {
        func_str = '=' + func_str
    }
    if (typeof y !== 'string') {
        y = Num2ColumnChar(y)
    }
    let sheet_name = app.ActiveSheet.Name
    let var_name_list = new Set(func_str.match(/{([^}]+)}/g))
    for (let item of var_name_list) {
        const strItem = item as string
        let dest = get_position_of_para(strItem.substring(1, strItem.length - 2), y, sheet_name, sp)
        func_str = func_str.replaceAll(strItem, dest)
    }
    return func_str
}

export function form_formula2(func_str, y, row) {
    /**
     * 替换{row-1}之类的参数
     */
    if (!func_str.startsWith('=')) {
        func_str = '=' + func_str
    }
    if (typeof y !== 'string') {
        y = Num2ColumnChar(y)
    }
    let var_name_list = new Set(func_str.match(/{([^}]+)}/g))
    let dest
    for (let item of var_name_list) {
        const itemStr = item as string
        dest = y + (row + parseInt(itemStr.split('+')[1]))
        if (dest.endsWith('NaN')) {
            // 说明表达式中没有+号
            dest = y + (row - parseInt(itemStr.split('-')[1]))
            if (dest.endsWith('NaN')) {
                // 说明表达式中没有-号
                dest = y + row
            }
        }

        func_str = func_str.replaceAll(itemStr, dest)
    }
    return func_str
}

function watch_var(var_name, pos, sp) {
    /**
     * 添加监视，当var_name所代表的参数有了计算结果后，会自动将pos对应的excel单元格的数值指向var_name所在的位置
     * @param var_name: 形如"{main_steam.F}"这样的变量名
     * @param pos: 形如"汽机!{Y}23"这样的Excel单元索引
     */
    if (!var_name) {
        console.log('systemdemo.js -> watch_var()监视的测点不存在：' + var_name)
        return null
    }
    let point = sp[var_name.split('.')[0]]
    let para = var_name.split('.')[1]
    if (!point['watch']) { // 如果当前测点没有watch属性，就添加
        point['watch'] = {}
    }
    let watch = point.watch[para] // 如果当前测点的watch属性
    if (!watch) {
        point.watch[para] = [pos]
    } else {
        if (pd.type(watch) === 'Array') {
            if (!lodash.includes(watch, pos)) {
                point.watch[para].push(pos)
            }
        }
    }
}

export function add_delete_flag(row, sheet_name = undefined) {
    /**
     *  给指定行添加删除标记，后续会删除该行，两种调用方式
     *  add_delete_flag("汽机!{Y}21")
     *  add_delete_flag(12, "汽机")
     */
    let sheet
    if (sheet_name) {
        sheet = sheet_name
    }
    try {
        let _sd = row.split('!{Y}')
        sheet = _sd[0]
        row = _sd[1]
        set_cell_value(parseInt(row), 2, '即将删除', sheet)
    } catch (e) {
        // console.log(e)
        set_cell_value(parseInt(row), 2, '即将删除', sheet_name)
    }

}

export function delete_row_by_flag(rows, sheet_name = undefined) {
    /**
     * 根据rows中的行是否带删除标记，按需删除相应行
     */
    let row_idx_list = []
    for (let row of rows) {
        if (get_cell_value(row, 2, sheet_name) === '即将删除') {
            row_idx_list.push(parseInt(row))
        }
    }
    delete_rows_of_sheet(row_idx_list, sheet_name)
}

export function delete_rows_of_sheet(row_idx_arr, sheet_name = undefined) {
    /**
     * 删除表格行，其余行上移
     */
    let rows = row_idx_arr.sort().reverse()
    for (let row of rows) {
        delete_row(row, sheet_name)
    }
}

function extract_from(string = '高压缸', para = 'F', y, sp) {
    /**
     * 查找所有从"高压缸"抽汽的抽汽点的”F"之和，返回的是索引
     */
    let formula = ''
    let result
    for (let j = 0; j < 10; j++) {  // 凡是从高压缸抽走的蒸汽流量都要扣掉
        let from
        let io = sp['extract_' + j].io
        if (io) {
            from = io['from']
            if (from === string) {
                formula = formula + '+{extract_' + j + '.' + para + '}'
            }
        }
    }
    if (formula !== '') {
        result = form_formula1(formula, y, sp)
        if (result.startsWith('=')) { // 删除form_formula1返回公式的等号
            result = result.substr(1)
        }
        result = result.trim()
        if (result.startsWith('+')) { // 删除表达式最前面的“+”号
            result = result.substr(1)
        }
        if (lodash.includes(result, '+')) { // 如果表达式是多个元素之和，则需要使用小括号括起来
            result = '(' + result + ')'
        }
    } else {
        result = 0
    }
    return result
}

export function auto_fit_columns() {
    /**
     * 自动调整列宽
     */
    app.ActiveSheet.Columns.AutoFit()
}

/**
 * 功能和fill_cell()相同，但是通过index可以指定将新的公式对应的参数的position放在现有参数位置的最前方
 * @param row
 * @param col
 * @param flag
 * @param expression
 * @param action
 * @param sp
 * @param index
 */
export function fill_cell_ex(row, col, flag, expression, action: string | number = 'delete', sp, index = true) {
    if (index) {
        if (flag === 'formula') {
            let _split_ = split(expression, '=', 2)
            let var_name = _split_[0].replace('{', '').replace('}', '').trim()
            let point_name = var_name.split('.')[0]
            let para = var_name.split('.')[1]
            let _q_std = get_position_of_para(var_name, undefined, '任何sheet', sp, 'all')
            if (_q_std) {
                sp[point_name]['href'][para] = []
            }
            fill_cell(row, col, flag, expression, action, sp)
            if (_q_std) {
                sp[point_name]['href'][para] = [...sp[point_name]['href'][para], _q_std]
            }
        }
    } else {
        fill_cell(row, col, flag, expression, action, sp)
    }
}

export function fill_cell(row, col, flag, expression, action: string | number | undefined = 'delete', spRef, fetch = 'first') {
    /**
     * 填充指定单元格，默认填充当前激活的工作簿
     *
     * flag === "para" 表示单元格是单个测点的引用，如expression = "main_steam.P"
     * flag === "formula" 表示单元格是公式，如expression = "{main_steam.H}=H_PT({row-2}, {row-1})"，其中{row}是传入的第一个参数row
     * expression = "{main_steam.H}=H_PT({Y}10, {Y}11)"
     *
     * formula表达式中的大括号表示具有特殊意义的变量，目前有三类：
     * 1. {main_steam.H}表示{测点.变量}，代表某个测点上的变量名
     * 2. {row-1}表示相对当前行的上下某一行，如-1表示上一行，-2表示上两行，+1表示下一行等
     * 3. {extract(from='高压缸').F}，表示所有从'高压缸'抽汽的抽汽测点的流量之和
     *
     * action="delete"且flag="formula"时，公式只能为简单的加减运算，如果公式中存在不可解析的参数，则会删除该参数的引用，例如公式为
     * "{leakage.高压门杆漏气总量}={leakage.高压门杆一漏}+{leakage.高压门杆二漏}"
     * 当leakage.高压门杆二漏不存在时，公式会变为"{leakage.高压门杆漏气总量}={leakage.高压门杆一漏}"
     * 当公式右侧所有的引用都不存在时，则该行公式会被打上删除标记，在后续处理中，该行会被删除。
     * 如果action="delete"，且flag="para"，则当引用的参数不存在时，该行会被标记删除
     *
     * action="watch"（只有当flag="para"时，action才可以取该值）表示监测目标参数，当目标参数有值时，再填充该行，例如当前单元格引用
     * 了“main_steam.F"时，如果main_steam.F不存在，常规引用会报错。如果设置action="watch"，后续计算出了main_steam.F时，会回过
     * 头来再处理该行，将计算出的值的引用位置填入该行，相当于先用后计算。
     *
     * action取其他值时，如果引用参数不存在，则默认取值为action
     *
     * 当flag="para"时，可以通过"{point.para}.(var_name:row-i)的方式向该方法内传入参数，当point.para不存在时，调用其他方法进
     * 行point.para的获取，小括号中的参数为传递给其他方法的参数。此时，其他方法可以返回一个公式，以"formula:"开头，公式主体将作为参数
     * 引用填入指定的单元格。
     *
     * @param row: 填充单元的行号，如果为null，则取当前激活的行
     * @param col: 填充单元格的列号，如果为null，则取当前激活的列
     * @param flag: "para"表示参数，"formula"表示公式
     * @param expression: 表示参数或公式的字符串
     * @param action: 当参数或公式中引用了不可解析的参数时，执行的操作
     * @param sp: 表示当前工作簿的参数信息
     * @param fetch: 表示获取参数的位置信息时，取第一个、最后一个还是所有
     */
    if (row === null) {
        row = app.ActiveCell.Row
    }
    if (col === null) {
        col = app.ActiveCell.Column
    }
    // console.log(sp)
    let sp = unref(spRef) // 如果spRef是响应式数据，则需要解引用
    // console.log(action)
    let y_char = Num2ColumnChar(col)
    if (flag === 'para') {
        // 表示该单元是单个测点的引用
        // console.log(`填充的表达式为：${expression}`)
        let pos = get_position_of_para(expression, y_char, null, sp, fetch)
        if (pos) {
            if (pos.startsWith('formula:')) {
                // 说明返回的位置信息是一个公式，漏气信息一般会返回公式，虽然想获取参数，但是参数不存在时，调用其他方法返回公式
                pos = pos.replace('formula:', '')
                app.Cells.Item(row, col).Formula = pos
            } else {
                // 判断pos是否和当前单元格的位置相同
                console.log(`要填充的数据为：pos=${pos}`)
                console.log('当前行的位置为：', `${row}, ${col}`)
                console.log(`要填充的数据的行号为：${get_row_from_pos_string(pos)}`)
                console.log(`要填充的数据的表名为：${get_sheet_name_from_pos_string(pos, app.ActiveSheet.Name)}`)
                if (get_row_from_pos_string(pos) === row && get_sheet_name_from_pos_string(pos, app.ActiveSheet.Name) === app.ActiveSheet.Name) {
                    const poss: string[] = get_position_of_para(expression, y_char, null, sp, 'all')
                    for (let pos_ of poss) { // 查找一个不等于当前pos的引用位置
                        if (pos_ !== pos) {
                            pos = pos_
                        }
                    }
                }
                app.Cells.Item(row, col).Formula = '=' + pos
            }
        } else {
            console.log(`${expression}不存在，action=${action}`)
            if (action === 'watch') { // 如果指定了替代的默认值，则使用该值
                // 填充该cell时，对应的参数可能还不存在值，将该行加入检测参数的触发行中，当监测参数有值后，会自动回过头来填充该行
                app.Cells.Item(row, col).Value2 = 'watching ' + expression
                watch_var(expression, app.ActiveSheet.Name + '!{Y}' + row, sp)
            } else if (action === 'delete') {
                add_delete_flag(app.ActiveSheet.Name + '!{Y}' + row)
                return true  // 如果要删除，就不需要设置该行格式了，直接返回
            } else {
                app.Cells.Item(row, col).Value2 = action
                // set_position_of_para(expression, app.ActiveSheet.Name + '!{Y}' + row, sp) // 如果是公式，则默认存储公式对应的参数测点
            }
        }
        set_position_of_para(expression, app.ActiveSheet.Name + '!{Y}' + row, sp)
    } else if (flag === 'formula') {
        expression = expression.replace(/{Y}/g, y_char)
        // console.log(`expression = ${expression}, y_char = ${y_char}`)
        let _split_ = split(expression, '=', 2)
        let para = _split_[0].replace('{', '').replace('}', '').trim()
        let formula = _split_[1]
        let sheet_name = app.ActiveSheet.Name
        let var_name_list = new Set(formula.match(/{([^}]+)}/g))
        let dest
        for (let item of var_name_list) {
            let itemStr = item as string
            if (lodash.includes(itemStr, 'row')) { // 处理形如{row+1}的变量
                dest = y_char + (row + parseInt(itemStr.split('+')[1]))
                if (dest.endsWith('NaN')) {
                    // 说明表达式中没有+号
                    dest = y_char + (row - parseInt(itemStr.split('-')[1]))
                    if (dest.endsWith('NaN')) {
                        // 说明表达式中没有-号
                        dest = y_char + row
                    }
                }
                formula = formula.replaceAll(itemStr, dest)
            } else if (lodash.includes(itemStr, '(')) {  // 处理形如 {extract_from("高压缸").F}的变量
                itemStr = itemStr.replace('{', '').replace('}', '')
                let _sd_ = itemStr.split('.')
                let exp = _sd_[0].substring(0, _sd_[0].length - 1) + ',\'' + _sd_[1] + '\',\'' + y_char + '\')'
                let ex_ = eval(exp)
                if (ex_ === 0) {
                    formula = formula.replace(itemStr, '0')
                } else {
                    formula = formula.replace('{' + itemStr + '}', ex_)
                }
            } else { // 处理形如{main_steam.F}的变量
                const var_name = itemStr.substring(1, itemStr.length - 1)
                if (var_name === '') {
                    console.error(`变量名为空，itemStr = ${itemStr}`)
                }
                // console.log(`formula = ${formula}`)
                dest = get_position_of_para(var_name, col, sheet_name, sp, fetch)
                if (dest) {
                    formula = formula.replaceAll(itemStr, dest)
                } else {
                    if (formula.startsWith(itemStr)) {
                        formula = formula.replace(itemStr, '')
                        if (formula.trim().startsWith('+')) {
                            formula = formula.trim().substring(1)  // 删除公式开头的+号
                        }
                    } else {
                        let idx = formula.indexOf(itemStr)
                        let before_op = formula.substring(idx - 1, idx)
                        if (before_op === '+' || before_op === '-' || before_op === '*' || before_op === '/') {
                            formula = formula.replace(formula.substring(idx, idx + itemStr.length + 1), '')
                        } else {
                            formula = formula.replace(itemStr, '')
                        }
                    }
                }
            }
        }
        formula = formula.trim()
        if (!formula) {  // 如果formula为空
            add_delete_flag(app.ActiveSheet.Name + '!{Y}' + row)
            return true
        }
        if (!formula.startsWith('=')) {
            formula = '=' + formula
        }
        app.Cells.Item(row, col).Formula = formula
        if (para !== '') { // 如果有参数名，则存储公式对应的参数测点
            // console.log(sp)
            set_position_of_para(para, app.ActiveSheet.Name + '!{Y}' + row, sp) // 如果是公式，则默认存储公式对应的参数测点
        }
    }

    // ----------------------- 处理数据格式 ------------------------------------
    let unit = get_cell_value(row, 3)
    if (lodash.includes(['MPa', 't/h', 'kJ/(kgK)', 'MW', 'kJ/h'], unit)) {
        app.Cells.Item(row, col).NumberFormat = '0.000'
    } else if (lodash.includes(['%', 'm3', 'h'], unit)) {
        app.Cells.Item(row, col).NumberFormat = '0.00'
    } else if (lodash.includes(['℃', 'kJ/kg', 'mm', 'kJ/(kW.h)'], unit)) {
        app.Cells.Item(row, col).NumberFormat = '0.0'
    } else if (lodash.includes(['y.m.d'], unit)) {
        app.Cells.Item(row, col).NumberFormat = 'yyyy-m-d'
    } else if (lodash.includes(['hh:mm'], unit)) {
        app.Cells.Item(row, col).NumberFormat = 'hh:mm'
    } else if (lodash.includes(['MJ/h'], unit)) {
        app.Cells.Item(row, col).NumberFormat = '0.000'
    }
    activate_sheet(get_active_sheet_name())
    return true
    // ======================== 处理数据格式 =====================================
}

export function showTaskPane(path = 'demotaskpane', title = '任务栏', update_if_exist = false) {
    /**
     * @param path: 面板组件的url路径
     * @param title: 面板组件的标题
     * @param update_if_exist: 如果面板存在，是否更新，默认存在则隐藏面板，如果为true，则存在时会强制刷新面板
     * @type {string[]}
     */
        // 默认展示ID=taskpane_id对应的网页
        // 每次请求都重新刷新网页，以保证数据都是当前激活工作簿的数据
        //
    let name = path.split('/') as unknown as string
    let tsId = Number(wps.PluginStorage.getItem(name)) // 通过面板名获取面板的id
    if (!tsId) {
        let taskpane = wps.CreateTaskPane(Util.GetUrlPath() + path, title) // 没有则加载id对应的网页，这里的name对应vue路由
        let id = taskpane.ID
        wps.PluginStorage.setItem(name, id) // 设置面板名对应的id值
        taskpane.Visible = true
    } else {
        let taskPane = wps.GetTaskPane(tsId)
        if (taskPane.Visible) {
            taskPane.Visible = false
            taskPane.Delete()
            wps.PluginStorage.setItem(name, null)
        }
        if (update_if_exist) {
            taskPane = wps.CreateTaskPane(Util.GetUrlPath() + path, title) // 没有则加载id对应的网页，这里的name对应vue路由
            let id = taskPane.ID
            wps.PluginStorage.setItem(name, id) // 设置面板名对应的id值
            taskPane.Visible = true
        }
    }
}

/**
 * 将形如"-汽机!{Y}6+汽机!{Y}7"这样的字符串转换为实际的excel单元格引用字符串，如当col为E时，返回"-汽机!E6+汽机!E7"
 * @param hrefStr
 * @param col
 */
export const hrefStr2ActualStr = (hrefStr: string, col: number | string) => {
    return hrefStr.replaceAll('{Y}', Num2ColumnChar(col))
}

/**
 * 冻结窗格
 * @param sheet_name
 * @param row
 * @param col
 */
export const freezePanes = (sheet_name: string, row: number, col: number | string) => {
    const sheet = get_sheet_by_name(sheet_name)
    activate_sheet(sheet_name)
    sheet.Range(`${Num2ColumnChar(col)}${row}`).Select()
    app.ActiveWindow.FreezePanes = true
}

export default {
    openOfficeFileFromSystemDemo,
    InvokeFromSystemDemo,
    refreshTestPoints,
    // refreshOriginPoints,
    RGB,
    freezePanes,
    hrefStr2ActualStr,
    createTurbineSheet,
    activate_sheet,
    getLastRowIndexOfSheet,
    getLastColumnIndexOfSheet,
    get_row_from_pos_string,
    app, set_position_of_para,
    get_active_sheet_name,
    get_sheet_by_name,
    get_cell_value,
    set_cell_value,
    create_new_sheet,
    update_project,
    hide_sheet,
    show_sheet,
    self_debug,
    get_value_of_point,
    get_cell_value_by_pos,
    get_position_of_para,
    get_test_point_name,
    dispatch_point_to_para,
    fill_cell,
    read_sheet,
    delete_row_by_flag,
    showTaskPane,
    Num2ColumnChar,
    ColumnChar2Num,
    set_cells,
    clear_sheet,
    auto_fit_columns,
    add_title_region,
    exist_sheet,
    scanDesignPoints,
    set_cell_format,
    fill_cell_ex,
    getUsedRangeOfSheet,
    goto
}